﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mapbox.Unity.MeshGeneration.Data;
using Mapbox.Unity.Map;
using Mapbox.Map;
using Mapbox.Utils;
using Mapbox.Unity.MeshGeneration.Factories;
using System;
using Mapbox.Unity.MeshGeneration.Enums;

public class SpawnRoute : MonoBehaviour
{
    private AbstractMap _map;
    private AbstractMap Map
    {
        get
        {
            if (_map == null)
            {
                _map = FindObjectOfType<AbstractMap>();
            }
            return _map;
        }
    }

    private LineRenderer line;
    private LineRenderer Line
    {
        get
        {
            if (line == null)
            {
                line = new GameObject().AddComponent<LineRenderer>();
                line.transform.SetParent(transform, false);
            }
            return line;
        }
    }

    private int _sampleCount = 40;
    private List<Vector3> _newVertexList;
    private List<Vector3> _newNormalList;
    private List<Vector2> _newUvList;
    private List<int> _newTriangleList;
    [SerializeField]
    private string _mapId = "";
    private MapImageType _mapIdType;

    // Use this for initialization
    void Start()
    {
        _newVertexList = new List<Vector3>(_sampleCount * _sampleCount);
        _newNormalList = new List<Vector3>(_sampleCount * _sampleCount);
        _newUvList = new List<Vector2>(_sampleCount * _sampleCount);
        _newTriangleList = new List<int>();
    }

    public void Spawn()
    {
        SpawnTile();
        SpawnRouteLine();
    }

    private void SpawnRouteLine()
    {
        Line.SetPositions(GetPoints());
    }

    private Vector3[] GetPoints()
    {
        Vector3[] points = new Vector3[RouteModel.Instance.RoutePointCache.Count];
        for (int i = 0; i < RouteModel.Instance.RoutePointCache.Count; i++)
        {
            points[i] = GetWorldPosition(RouteModel.Instance.RoutePointCache[i]);
        }
        return points;
    }

    //获取经纬度对应的世界坐标 
    private Vector3 GetWorldPosition(Vector2d coordinate)
    {
        return Map.GeoToWorldPosition(coordinate);
    }

    private void SpawnTile()
    {
        foreach (var item in RouteModel.Instance.RouteTileCache)
        {
            SpawnTileItem(item);
        }
    }

    private void SpawnTileItem(UnwrappedTileId tileId)
    {
        UnityTile unityTile = null;

        if (unityTile == null)
        {
            unityTile = new GameObject().AddComponent<UnityTile>();
            unityTile.transform.SetParent(transform, false);
            unityTile.gameObject.AddComponent<MeshRenderer>();
            unityTile.gameObject.AddComponent<MeshFilter>();
            CreateBaseMesh(unityTile);
            GetImageData(unityTile);
        }

        unityTile.Initialize(Map, tileId, Map.WorldRelativeScale, Map.AbsoluteZoom);
        SetTilePlace(tileId, unityTile, Map);

#if UNITY_EDITOR
        unityTile.gameObject.name = unityTile.CanonicalTileId.ToString();
#endif
    }

    private void SetTilePlace(UnwrappedTileId tileId, UnityTile tile, IMapReadable map)
    {
        var rect = tile.Rect;

        var scale = tile.TileScale;

        var position = new Vector3(
            (float)(rect.Center.x - map.CenterMercator.x) * scale,
            0,
            (float)(rect.Center.y - map.CenterMercator.y) * scale);
        tile.transform.localPosition = position;
    }


    private void CreateBaseMesh(UnityTile tile)
    {
        //TODO use arrays instead of lists
        _newVertexList.Clear();
        _newNormalList.Clear();
        _newUvList.Clear();
        _newTriangleList.Clear();

        for (float y = 0; y < _sampleCount; y++)
        {
            var yrat = y / (_sampleCount - 1);
            for (float x = 0; x < _sampleCount; x++)
            {
                var xrat = x / (_sampleCount - 1);

                var xx = Mathd.Lerp(tile.Rect.Min.x, tile.Rect.Max.x, xrat);
                var yy = Mathd.Lerp(tile.Rect.Min.y, tile.Rect.Max.y, yrat);

                _newVertexList.Add(new Vector3(
                    (float)(xx - tile.Rect.Center.x) * tile.TileScale,
                    0,
                    (float)(yy - tile.Rect.Center.y) * tile.TileScale));
                _newNormalList.Add(Mapbox.Unity.Constants.Math.Vector3Up);
                _newUvList.Add(new Vector2(x * 1f / (_sampleCount - 1), 1 - (y * 1f / (_sampleCount - 1))));
            }
        }

        int vertA, vertB, vertC;
        for (int y = 0; y < _sampleCount - 1; y++)
        {
            for (int x = 0; x < _sampleCount - 1; x++)
            {
                vertA = (y * _sampleCount) + x;
                vertB = (y * _sampleCount) + x + _sampleCount + 1;
                vertC = (y * _sampleCount) + x + _sampleCount;
                _newTriangleList.Add(vertA);
                _newTriangleList.Add(vertB);
                _newTriangleList.Add(vertC);

                vertA = (y * _sampleCount) + x;
                vertB = (y * _sampleCount) + x + 1;
                vertC = (y * _sampleCount) + x + _sampleCount + 1;
                _newTriangleList.Add(vertA);
                _newTriangleList.Add(vertB);
                _newTriangleList.Add(vertC);
            }
        }
        var mesh = tile.MeshFilter.mesh;
        mesh.SetVertices(_newVertexList);
        mesh.SetNormals(_newNormalList);
        mesh.SetUVs(0, _newUvList);
        mesh.SetTriangles(_newTriangleList, 0);
    }

    private void GetImageData(UnityTile tile)
    {
        if (_mapIdType == MapImageType.None)
            return;
        bool _useRetina = false;

        RasterTile rasterTile;
        if (_mapId.StartsWith("mapbox://", StringComparison.Ordinal))
        {
            rasterTile = _useRetina ? new RetinaRasterTile() : new RasterTile();
        }
        else
        {
            rasterTile = _useRetina ? new ClassicRetinaRasterTile() : new ClassicRasterTile();
        }

        tile.RasterDataState = TilePropertyState.Loading;

        tile.AddTile(rasterTile);
        rasterTile.Initialize(Map.FileSource, tile.CanonicalTileId, _mapId, () =>
        {
            if (tile == null)
            {
                return;
            }

            if (rasterTile.HasError)
            {
                tile.RasterDataState = TilePropertyState.Error;
                return;
            }

            tile.SetRasterData(rasterTile.Data, false, false);
            tile.RasterDataState = TilePropertyState.Loaded;
        });
    }
}
